using Gadgeteer.Modules;
using Gadgeteer;
using Microsoft.SPOT.Hardware;
using System.Collections;
using System;

namespace dotnetwarrior.NetMF.DL40
{
  public partial class DLIOModule : DaisyLinkModule
  {
    private const byte BaseRegisterSize = 9;
    private const byte PinCount = 42;
    private const byte DAISYLINK_MANUFACTURER = 0x10;
    private const byte DAISYLINK_TYPE_GENERIC = 0x01;
    private const byte DAISYLINK_VERSION_GENERIC = 0x01;

    private Hashtable _pinRegister = new Hashtable();
    private Hashtable _interruptPins = new Hashtable();
    private InterruptPin[] _interruptPinCache = new InterruptPin[PinCount];
    private int _interruptPinCount;

    // Preallocated buffers to limit GC
    private byte[] _oneByte = new byte[1];
    private byte[] _eightBytes = new byte[8];

    // Note: A constructor summary is auto-generated by the doc builder.
    /// <summary></summary>
    /// <param name="socketNumber">The socket that this module is plugged in to.</param>
    public DLIOModule(int socketNumber)
      : base(socketNumber, DAISYLINK_MANUFACTURER, DAISYLINK_TYPE_GENERIC, DAISYLINK_VERSION_GENERIC, DAISYLINK_VERSION_GENERIC, 50, "Generic")
    {
      Socket socket = Socket.GetSocket(socketNumber, true, this, null);
      DaisyLinkInterrupt += new DaisyLinkInterruptEventHandler(DLIOModule_DaisyLinkInterrupt);
    }

    /// <summary>
    /// Creates an Input Pin for the specified port and pin on the module
    /// </summary>
    /// <param name="port">GPIO Port</param>
    /// <param name="pin">GPIO Pin on the port</param>
    /// <param name="resistor">Resistor mode of the pin</param>
    /// <returns>Concrete implementation of IInputPin</returns>
    public IDigitalPin CreateInputPin(byte port, byte pin, Port.ResistorMode resistor)
    {
      return new InputPin(this, port, pin, resistor);
    }

    /// <summary>
    /// Creates an Interrupt Pin for the specified port and pin on the module
    /// </summary>
    /// <param name="port">GPIO Port</param>
    /// <param name="pin">GPIO Pin on the port</param>
    /// <param name="resistor">Resistor mode of the pin</param>
    /// <param name="interruptMode">Interrupt events that this pin is sensitive to</param>
    /// <returns>Concrete implementation of IInterruptPin</returns>
    public IInterruptPin CreateInterruptPin(byte port, byte pin, Port.ResistorMode resistor, Port.InterruptMode interruptMode)
    {
      return new InterruptPin(this, port, pin, resistor, interruptMode);
    }

    /// <summary>
    /// Creates an Ouput Pin for the specified port and pin on the module
    /// </summary>
    /// <param name="port">GPIO Port</param>
    /// <param name="pin">GPIO Pin on the port</param>
    /// <param name="initialState">Initial state of the pin</param>
    /// <returns>Concrete implementation of IOutputPin</returns>
    public IOutputPin CreateOutputPin(byte port, byte pin, bool initialState)
    {
      return new OutputPin(this, port, pin, initialState);
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="pinId"></param>
    /// <param name="resistor"></param>
    /// <param name="interruptMode"></param>
    /// <returns></returns>
    public IPulseCounterPin CreateCapturePin(
      CapturePinId pinId,
      Port.ResistorMode resistor,
      Port.InterruptMode interruptMode,
      bool resetOnRead = false)
    {
      return new CapturePin(this, (byte)((byte)pinId / 12), (byte)((byte)pinId % 12), resistor, interruptMode, resetOnRead);
    }

    /// <summary>
    /// Creates a Counter Pin for the specified port and pin on the module
    /// </summary>
    /// <param name="port">GPIO Port</param>
    /// <param name="pin">GPIO Pin on the port</param>
    /// <param name="resistor">Resistor mode of the pin</param>
    /// <param name="interruptMode">Interrupt events that this pin counts</param>
    /// <returns>Concrete implementation of ICounterPin</returns>
    public IPulseCounterPin CreateCounterPin(
      byte port,
      byte pin,
      Port.ResistorMode resistor,
      Port.InterruptMode interruptMode,
      bool resetOnRead = false)
    {
      return new PulseCounterPin(this, port, pin, resistor, interruptMode, resetOnRead);
    }

    /// <summary>
    /// Create ADC Pin for the specified port and pin on the module
    /// </summary>
    /// <param name="pinId">GPIO Pin and Port</param>
    /// <returns></returns>
    public IAnalogInputPin CreateAnalogInputPin(AnalogInputPinId pinId)
    {
      return new AnalogInputPin(this, pinId);
    }

    public IPWMOutputPin CreatePWMOutputPin(PWMOutputPinId pinId)
    {
      switch (pinId)
      {
        case PWMOutputPinId.PWM16_0_8:
        case PWMOutputPinId.PWM16_0_9:
        case PWMOutputPinId.PWM16_0_10:
        case PWMOutputPinId.PWM16_1_10:
         return new PWMOutputPin(this, pinId, 1365312);

        case PWMOutputPinId.PWM32_1_1:
        case PWMOutputPinId.PWM32_1_3:
         return new PWMOutputPin(this, pinId, uint.MaxValue);
      }

      throw new ArgumentException("pinId");
    }

    private void DLIOModule_DaisyLinkInterrupt(DaisyLinkModule sender)
    {
      for (int i = 0; i < _interruptPinCount; ++i)
      {
        InterruptPin pin = _interruptPinCache[i];

        uint interruptCount = pin.GetInterruptCount();
        if (interruptCount > 0)
        {
          pin.OnInterrupt(pin, interruptCount);
        }
      }
    }

    private void RegisterPin(IPin pin, bool interrupt)
    {
      if (_pinRegister.Contains(pin.Id)) throw new InvalidOperationException("Pin already instantiated.");

      _pinRegister.Add(pin.Id, pin);

      if (interrupt)
      {
        if (!_interruptPins.Contains(pin.Id))
        {
          _interruptPins.Add(pin.Id, pin);
          _interruptPins.Values.CopyTo(_interruptPinCache, 0);
          _interruptPinCount++;
        }
      }
    }

    private void UnregisterPin(IPin pin)
    {
      if (!_pinRegister.Contains(pin.Id))
      {
        _pinRegister.Remove(pin.Id);
        _interruptPins.Values.CopyTo(_interruptPinCache, 0);
        _interruptPinCount--;
      }

      if (!_interruptPins.Contains(pin.Id))
      {
        _interruptPins.Remove(pin.Id);
      }
    }

    private void WriteByte(byte register, byte value)
    {
      WriteParams(register, value);
    }

    private byte ReadByte(byte register)
    {
      return this.Read(register);
    }

    private ushort ReadUInt16(byte register)
    {
      int bytesWritten;
      int bytesRead;

      _oneByte[0] = register;
      this.WriteRead(_oneByte, 0, 1, _eightBytes, 0, 2, out bytesWritten, out bytesRead);
      return (ushort)(_eightBytes[0] | (_eightBytes[1] << 8));
    }

    private uint ReadUInt32(byte register)
    {
      int bytesWritten;
      int bytesRead;

      _oneByte[0] = register;

      this.WriteRead(_oneByte, 0, 1, _eightBytes, 0, 4, out bytesWritten, out bytesRead);
      return (uint)(_eightBytes[0] | (_eightBytes[1] << 8) | (_eightBytes[2] << 16) | (_eightBytes[3] << 24));
    }

    private ulong ReadUInt64(byte register)
    {
      int bytesWritten;
      int bytesRead;

      _oneByte[0] = register;

      this.WriteRead(_oneByte, 0, 1, _eightBytes, 0, 8, out bytesWritten, out bytesRead);
      return (ulong)(_eightBytes[0] | (_eightBytes[1] << 8) | (_eightBytes[2] << 16) | (_eightBytes[3] << 24)
         | (_eightBytes[4] << 32) | (_eightBytes[5] << 40) | (_eightBytes[6] << 48) | (_eightBytes[7] << 56));
    }
  }

  /// <summary>
  /// Base interface for pins
  /// </summary>
  public interface IPin
  {
    /// <summary>
    /// Pin ID on the MCU 
    /// </summary>
    byte Id { get; }

    /// <summary>
    /// GPIO port that this pin belongs to
    /// </summary>
    byte PinPort { get; }

    /// <summary>
    /// Pin number on the GPIO port
    /// </summary>
    byte PinNumber { get; }

    /// <summary>
    /// Dispose port
    /// </summary>
    void Dispose();
  }

  /// <summary>
  /// Interface for an Input pin on the DL40
  /// </summary>
  public interface IDigitalPin : IPin
  {
    /// <summary>
    /// Read the current state of the pin
    /// </summary>
    /// <returns>true if the pin in hgh otherwise false</returns>
    bool Read();
  }

  /// <summary>
  /// Interface for an Output pin on the DL40
  /// </summary>
  public interface IOutputPin : IPin, IDigitalPin
  {
    /// <summary>
    /// Changes the state of the pin. true to make the pin logic high or false otherwise
    /// </summary>
    /// <param name="state"></param>
    void Write(bool state);
  }

  /// <summary>
  /// Interface for an Interrupt sensitive pin on the DL40
  /// </summary>
  public interface IInterruptPin : IPin, IDigitalPin
  {
    /// <summary>
    /// Interrupt raised when the pin state changes according to the signal sensitivity defined when creating the pin
    /// </summary>
    event PinInterruptHandler Interrupt;
  }

  /// <summary>
  /// Interface for a Pulse Counter pin on the DL40
  /// </summary>
  public interface IPulseCounterPin : IPin, IDigitalPin
  {
    /// <summary>
    /// Reads the current pulse count for the pin
    /// </summary>
    /// <returns>Current pulse count</returns>
    ulong ReadCounter();

    /// <summary>
    /// Resets the counter to 0
    /// </summary>
    void ResetCounter();

    //void StopCounter();

    //void StartCounter();

    //void TriggerCounter(IInterruptPin startPin, IInterruptPin stopPin);
  }

  /// <summary>
  /// Interface for an Analog Input (ADC) pin on the DL40
  /// </summary>
  public interface IAnalogInputPin : IPin
  {
    /// <summary>
    /// Read the last 10bit analog value (0-1023)
    /// </summary>
    /// <returns>10bit analog reading</returns>
    ushort ReadRaw();

    /// <summary>
    /// Read the last analog value as a percentage 0.0 - 1.0
    /// </summary>
    /// <returns>Analog reading</returns>
    double Read();
  }

  /// <summary>
  /// Interface for a PWM pin on the DL40
  /// </summary>
  /// <remarks>
  /// The following groups of pins share the same clock and therefore changing the 
  /// frequency for the pin affects the frequency for the other pins.
  /// Group 1:
  ///   PWM16_0_8
  ///   PWM16_0_9
  ///   PWM16_0_10
  /// Group 2:
  ///   PWM32_1_1 
  ///   PWM32_1_3
  /// Group 3:
  ///   PWM16_1_10
  /// </remarks>
  public interface IPWMOutputPin : IPin
  {
    /// <summary>
    /// Set the frequency and duty cycle of the PWM signal for the pin.
    /// </summary>
    /// <param name="frequency">Frequency in Hz</param>
    /// <param name="dutyCycle">Duty cycle of the pulse as percentage of the frequency.</param>
    void Set(uint frequency, byte dutyCycle);

    /// <summary>
    /// Set the PWM signal period and high time.
    /// </summary>
    /// <param name="period_ns">Period in nanoseconds</param>
    /// <param name="highTime_ns">High time in nanoseconds</param>
    void SetPulse(uint period_ns, uint highTime_ns);
  }

  /// <summary>
  /// Delegate for interupts raised by the interrupt pins of the DL40 IO module
  /// </summary>
  /// <param name="pin">Interrupt pin instance that raised the event</param>
  /// <param name="interruptCount">Number of interrupts that have occured since the interrupt was initially triggered</param>
  public delegate void PinInterruptHandler(IInterruptPin pin, uint interruptCount);

  /// <summary>
  /// Capture Pins
  /// </summary>
  public enum CapturePinId : byte
  {
    Capture16_1_8 = 1 * 12 + 8,
    Capture16_0_2 = 0 * 12 + 2,
    Capture32_1_0 = 1 * 12 + 0,
    Capture32_1_5 = 1 * 12 + 5,
  }

  /// <summary>
  /// Analog input Pins
  /// </summary>
  public enum AnalogInputPinId : byte
  {
    PIO0_11 = 0 * 12 + 11,
    PIO1_0 = 1 * 12 + 0,
    PIO1_1 = 1 * 12 + 1,   
    PIO1_2 = 1 * 12 + 2,
    PIO1_4 = 1 * 12 + 4,
    PIO1_10 = 1 * 12 + 10,
    PIO1_11 = 1 * 12 + 11,
  }

  /// <summary>
  /// PWM output pins
  /// </summary>
  public enum PWMOutputPinId : byte
  {
    PWM16_0_8 = 0 * 12 + 8,
    PWM16_0_9 = 0 * 12 + 9,
    PWM16_0_10 = 0 * 12 + 10,
    PWM32_1_1 = 1 * 12 + 1,
    PWM32_1_3 = 1 * 12 + 3,
    PWM16_1_10 = 1 * 12 + 10,
  }
}
